# -*- coding: utf8 -*-
"""
Hopf algebra : Planar Binary Tree with multiplicity.

AUTHOR:

- Jean-Baptiste Priez
"""
#*****************************************************************************
#       Copyright (C) 2012 Jean-Baptiste Priez <jbp@kerios.fr>,
#
#  Distributed under the terms of the GNU General Public License (GPL)
#
#    This code is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    General Public License for more details.
#
#  The full text of the GPL is available at:
#
#                  http://www.gnu.org/licenses/
#*****************************************************************************
from good_hopf_algebra import GoodHopfAlgebras
#from pattern import FundamentalBasis, FundamentalDualBasis, \
#    getitem_for_wordlike_indices
from sage.categories.graded_hopf_algebras_with_basis import \
    GradedHopfAlgebrasWithBasis
from sage.categories.realizations import Category_realization_of_parent
from sage.combinat.free_module import CombinatorialFreeModule
from sage.misc.bindable_class import BindableClass
from sage.structure.parent import Parent
from sage.structure.unique_representation import UniqueRepresentation
from packed_words import PackedWord
from sage.combinat.binary_tree import LabelledBinaryTree
from sage.combinat.ncsf_qsym.qsym import QuasiSymmetricFunctions

def getitem_tree( self, c, *rest ):
    from sage.rings.integer import Integer
    from sage.sets.set import Set
    if isinstance( c, ( int, Integer ) ):
        assert( c == 1 ), "it must be ..."
        res = self.indices()( [], label = 1 )
    elif len( c ) > 1 and all( [isinstance( i, ( int, Integer ) ) for i in c] ):
        s = Set( c )
        res = self.indices()( self.phi( list( c ) + list( rest ) ) )
    elif len( c ) == 0 and len ( rest ) == 0:
        res = self.phi( [] )
    else:
        raise ValueError( "Un arbre à multiplicité ou un mot tassé..." )
    return self.monomial( res )

def latex_term( self, m ):
    from sage.misc.latex import latex
    if len( m ) == 0 :
        return "1"
    return self.print_options()['latex_prefix'] + "_{\\vcenter{\\hbox{\\scalebox{.4}\n{" + latex( m ) + "}}}}"

def repr_term( self, c ):
    """
    The default implementation gives double brackets
    """
    s = canonical_packed_word( c )
    return self.prefix() + str( s ).replace( " ", "" )

def canonical_packed_word( bt ):
    ''' Make the canonical packed word of bt
    EXAMPLES::
    '''
    def pr( bt, res, i = [1] ):
        if not bt[0].is_empty(): pr( bt[0], res )
        j = i[0]
        k = bt.label()
        i[0] += 1
        if not bt[1].is_empty(): pr( bt[1], res )
        res += [j for _ in range( k )]
    res = []
    if not  bt.is_empty() :
        pr( bt, res )
    return PackedWord( res )

def taiga_class( bt ):
    ''' Compute all packed word 'u' in the taiga class
    associate to 'bt'.

    EXAMPLES::

        sage: LT = LabelledBinaryTree
        sage: lt = LT([LT([],2), None],3)
        sage: taiga_class(lt)
        [[1, 1, 2, 2, 2], [1, 2, 1, 2, 2], [1, 2, 2, 1, 2], [2, 1, 1, 2, 2], [2, 1, 2, 1, 2], [2, 2, 1, 1, 2]]
    '''
    from sage.combinat.words.shuffle_product import ShuffleProduct_w1w2
    from sage.combinat.words.word import Word
    from packed_words import PackedWord
    SP = ShuffleProduct_w1w2
    def compute_shuffle( left, right, k, l ):
        def last_shuffle( ens, k, l ):
            ## (ens I_I_I l^{k-1}) . k
            # tmp <- union of {w I_I_I l^{k-1}} for w in ens
            km1 = Word( [l for _ in range( k - 1 )] )
            red = lambda list_iter: reduce( lambda li, iter : li + list( iter ), list_iter, [] )
            tmp = red( map( lambda w: SP( w, km1 ), ens ) )
            # tmp . k = {w_1, ... w_m} . k
            return map( lambda w: Word( list( w ) + [l] ), tmp )

        if len( left ) == 0 :
            if len( right ) == 0: return [Word( [l for _ in range( k )] )]
            return last_shuffle( right, k, l )
        if len( right ) == 0:
            return last_shuffle( left, k, l )
        res = []
        for w0 in left:
            for w1 in right:
                 res += last_shuffle( ShuffleProduct_w1w2( w0, w1 ), k, l )
        return res
    def infix_postfix_reading( bt, i = [1] ):
        """
        bt =  l^k
            /     \
           bt'    bt"
           l can be compute with an infix path on bt and k is the associate multiplicity.
        Recursivly : the set of packed words which make bt by insertion are the shuffle of
          packed word in bt' and in bt", shuffled with l^{k-1} and concatenate with l.
        """
        if bt.is_empty() : return [Word( [] )]
        left = []; right = []
        if not bt[0].is_empty():
            left = infix_postfix_reading( bt[0] )
        l = i[0]; i[0] += 1
        k = bt.label()
        if not bt[1].is_empty():
            right = infix_postfix_reading( bt[1] )
        return compute_shuffle( left, right, k, l )
    return map( lambda w : PackedWord( w ), infix_postfix_reading( bt ) )


class PlanarBinaryUnaryTreeFunctions( UniqueRepresentation, Parent ):
    '''

    '''
    def __init__( self, R ):
        '''
        EXAMPLES::

            sage: FQS = FreeQuasisymmetricFunctions(ZZ);FQS
            The Hopf algebra of free quasisymmetric functions over the Integer Ring
        '''
        from sage.categories.all import Rings
        assert( R in Rings() )
        self._base = R
        Parent.__init__( self, category = GoodHopfAlgebras( R ).WithRealizations() )

    def phi( self, indice ):
        '''
        EXAMPLES::

        '''
        from binary_unary_trees import MBST
        return MBST( indice )

    def indices( self ):
        '''
        EXAMPLES::

        '''
        from binary_unary_trees import BinaryUnaryTrees
        return BinaryUnaryTrees()

    def _repr_( self ):
        r"""
        EXAMPLES::

        """
        return "The Hopf algebra of planar binary trees functions over the %s" % self.base_ring()

    def a_realization( self ):
        return self.Pm()

    def __init_extra__( self ):
        '''
        # M (WQSym) <-> Qm (PBTm)
        from all import WQSym
        M = WQSym( self.base().base_ring() ).M()
        Qm = self.Qm()

        morph = lambda F, T, func, tri = None, comp = None: ( 
            F._module_morphism( func, codomain = T, triangular = tri, cmp = comp )
            if comp is not None else
            F._module_morphism( func, codomain = T, triangular = tri ) )
        ## M -> Qm
        M_to_Qm = morph( M, Qm, lambda pw: Qm.monomial( Qm.phi( pw ) ) )
        M_to_Qm.register_as_coercion()
        Qm_to_M = morph( Qm, M, lambda bt: M( canonical_packed_word( bt ) ) )
        Qm_to_M.register_as_coercion()

        # Qm <-> H ----> quotient pseudo permutohèdre
        H = self.H()
        from sage.sets.set import Set
        from binary_unary_trees import but_cmp
        Hm_to_Qm = morph( H, Qm,
                lambda bt : Qm.sum_of_monomials( bt.quasi_quot_greater() ),
                tri = "upper",
                comp = but_cmp )
        Hm_to_Qm.register_as_coercion()

        # Qm <-> E  ----> quotient pseudo permutohèdre
        E = self.E()
        from binary_unary_trees import but_cmp2
        Em_to_Qm = morph( E, Qm,
                lambda bt : Qm.sum_of_monomials( bt.quasi_quot_smaller() ),
                tri = "lower",
                comp = but_cmp2 )
        Em_to_Qm.register_as_coercion()

        # Qm <-> Fm
        from binary_unary_trees import but_cmp3
        A = self.A()
        Fm_to_Qm = morph( A, Qm,
                lambda bt : Qm.sum_of_monomials( bt.greater() ),
                tri = "upper",
                comp = but_cmp3 )
        Fm_to_Qm.register_as_coercion()

        A2 = self.A2()
        A2_to_Qm = morph( Qm, A2,
                lambda bt: Qm.sum_of_monomials( bt.greater() ),
                tri = "upper",
                comp = but_cmp3 )
        A2_to_Qm.register_as_coercion()
        ( ~A2_to_Qm ).register_as_coercion()

        # Qm <-> Gm
        from binary_unary_trees import but_cmp4
        B = self.B()
        Gm_to_Qm = morph( B, Qm,
                lambda bt : Qm.sum_of_monomials( bt.greater2() ),
                tri = "upper",
                comp = but_cmp4 )
        Gm_to_Qm.register_as_coercion()

        B2 = self.B2()
        Qm_to_B2 = morph( Qm, B2,
                lambda bt : B2.sum_of_monomials( bt.greater2() ),
                tri = "upper",
                comp = but_cmp4 )
        Qm_to_B2.register_as_coercion()
        ( ~Qm_to_B2 ).register_as_coercion()

        # Qm <-> C  ----> quotient pseudo permutohèdre
        C = self.C()
        Qm_to_Gb = morph( Qm, C, lambda bt: C.sum_of_monomials( bt.quasi_quot_greater() ), tri = "upper", comp = but_cmp )
        Qm_to_Gb.register_as_coercion()
        ( ~Qm_to_Gb ).register_as_coercion()

        # Qm <-> D ----> quotient pseudo permutohèdre avec intersection
        D = self.D()
        def greater_quot_inter( elem, real ):
            from sage.sets.set import Set
            ## elem.... ou real(elem)...
            couples = real( elem ).coproduct().monomial_coefficients().keys()
            list_of_trees = []
            for c in couples:
                list_of_trees += ( real( c[0] ) * real( c[1] ) ).monomial_coefficients().keys()
            s = Set( list_of_trees ).intersection( Set( elem.quasi_quot_greater() ) )
            return s
        Qm_to_D = morph( Qm, D, lambda bt: D.sum_of_monomials( greater_quot_inter( bt, Qm ) ), tri = "upper", comp = but_cmp )
        Qm_to_D.register_as_coercion()

        # Qm <-> E ----> quotient pseudo permutohèdre
        F = self.F()
        F_to_Qm = morph( F, Qm, lambda bt: Qm.sum_of_monomials( greater_quot_inter( bt, Qm ) ), tri = "upper", comp = but_cmp )
        F_to_Qm.register_as_coercion()
        ( ~F_to_Qm ).register_as_coercion()

        ##### Qm -> M (QSym)
        from sage.combinat.ncsf_qsym.qsym import QuasiSymmetricFunctions
        M = QuasiSymmetricFunctions( self.base().base_ring() ).M()
        def nom_a_trouver( bt ):
            from sage.sets.set import Set
            cpw = sorted( list( canonical_packed_word( bt ) ) )
            l1 = []; l2 = []
            for l in cpw:
                if l1.count( l ) == 0:
                    l2.append( cpw.count( l ) )
                    l1.append( l )
            return l2
        Qm_to_M = morph( Qm, M, lambda bt: M( nom_a_trouver( bt ) ) )
        Qm_to_M.register_as_coercion()
        '''

    _shorthands = set( ['Pm', 'Qm', 'H', 'E', 'A', 'B', 'C'] )

    class Bases( Category_realization_of_parent ):

        def super_categories( self ):
            R = self.base().base_ring()
            return [self.base().Realizations(),
                    GoodHopfAlgebras( R ).Realizations(),
                    GradedHopfAlgebrasWithBasis( R ).Realizations()]

        class ParentMethods:

            def one_basis( self ):
                return self.indices()()


    class Fundamental( BindableClass, CombinatorialFreeModule ):

        def __init__( self, PBTm ):
            CombinatorialFreeModule.__init__( 
                self, PBTm.base_ring(), PBTm.indices(),
                category = PBTm.Bases(),
                prefix = "Pm", latex_prefix = "\\mathbb{P}^m",
                bracket = False )

        _latex_term = latex_term
        _repr_term = repr_term
        __getitem__ = getitem_tree

        def product_on_basis( self, bt1, bt2 ):
            r"""
            The product of the Pm basis : shuffle of tree with multiplicities.
            
            EXAMPLES::
            
                Pm   * Pm       =    
                  2        1
                   \      / 
                    1    2
                      
                Pm      + Pm    + Pm      + Pm      + Pm    + Pm
                  2         2         2         1         1         1
                   \         \         \       /         /         /
                    1         1         1     2         2         2
                     \       /         /       \         \       /
                      1     1         2         1         2     2
                     /       \       /           \       /       \
                    2         2     1             2     1         1
            
            TESTS::
            
                sage: Pm = PBTm(QQ).Pm()
                sage: Pm[2,1,1]*Pm[1,1,2]
                Pm[3,3,4,2,1,1] + Pm[3,3,2,4,1,1] + Pm[2,3,3,4,1,1] + Pm[3,3,2,1,1,4] + Pm[2,3,3,1,1,4] + Pm[2,1,1,3,3,4]
            """
            from tree_shuffle_product import TreeShuffleProduct
            return self.sum_of_monomials( bt for bt in TreeShuffleProduct( bt1, bt2 ) )
#            from sage.sets.set import Set
#            from sage.combinat.words.word import Word
#            from sage.combinat.words.shuffle_product import ShuffleProduct_shifted
#            if not bt1 or not bt2 :
#                return self.monomial( bt1 if not bt2 else bt2 )
#            w1 = canonical_packed_word( bt1 )
#            w2 = [i + max( w1 ) for i in canonical_packed_word( bt2 )]
#            return self.sum_of_monomials( Set( [self.phi( s )
#                for s in ShuffleProduct_shifted( Word( w1 ), Word( w2 ) )] ) )

        def coproduct_on_basis( self, p ):
            r"""
            The coproduct of the P basis : deconcatenation of tree with multiplicities
            
            EXAMPLES::
            
                       / Pm      \
               delta   |     1   |   =
                       |    / \  |
                       \   2   1 /
                
                1 # Pm      + Pm  # Pm    + Pm  # Pm    + Pm  # Pm      + Pm    # Pm    +
                        1       2     1       1       1     1       1       1         1  
                       / \             \             /             / \       \       /   
                      2   1             1           2             1   1       1     1    
                 Pm    # Pm  + Pm      # 1 + Pm    # Pm    + Pm    # Pm
                   2       1       1             1       1       1     1
                    \             / \           /       /       /
                     1           2   1         1       1       2


            TESTS::

                sage: Pm = PBTm(QQ).Pm()
                sage: Pm[[]].coproduct()
                Pm[] # Pm[]
                sage: P[1,1,3,2].coproduct()
                P[] # P[1, 2, 4, 3] + P[1] # P[1, 3, 2] + P[1] # P[1, 2, 3] + P[2, 1] # P[1, 2] + P[1, 2] # P[2, 1] + P[1, 2] # P[1, 2] + P[1, 3, 2] # P[1] + P[1, 2, 3] # P[1] + P[1, 2, 4, 3] # P[]                
            """
            from sage.categories.tensor import tensor
            def extract_sub_tree_from_root( bt ):
                B = self.indices()
                ## empty tree
                if bt.is_empty():
                    yield ( [], bt ) # -> 1 # 1
                    return
                ## other trees
                ### -> bt # 1
                yield ( [bt], B() )
                ### (product of a list of subtrees of bt) #  bt'
                for ( l0, b0 ) in extract_sub_tree_from_root( bt[0] ):
                    for ( l1, b1 ) in extract_sub_tree_from_root( bt[1] ):
                        yield ( l0 + l1, B( [b0, b1], label = bt.label() ) )
                        for i in range(1, bt.label()):
                            yield ( l0 + [B(label = i)] + l1, B( [b0, b1], label = bt.label() - i ) )
            return self.tensor_square().sum( 
                map( lambda ( li, bt ): tensor( 
                            [self.prod( map( self.monomial, li ) ), self.monomial( bt ) ] ),
                    extract_sub_tree_from_root( p ) ) )
            
#            from packed_words import to_pack
#            from sage.sets.finite_set_map_cy import fibers
#            w = canonical_packed_word( p )
#            bt = self.phi( w )
#            classe = taiga_class( bt )
#            tab = [( to_pack( w[:i] ), to_pack( w[i:] ) ) for i in range( len( w ) + 1 ) for w in classe]
#            fib = fibers( lambda c: ( self.phi( c[0] ) , self.phi( c[1] ) ), tab )
#            return self.tensor_square().sum_of_monomials( fib )

    Pm = Fundamental

    class FundamentalDual( BindableClass, CombinatorialFreeModule ):

        def __init__( self, PBT ):
            CombinatorialFreeModule.__init__( 
                self, PBT.base_ring(), PBT.indices(),
                category = PBT.Bases(),
                prefix = "Qm", latex_prefix = "\\mathbb{Q}^m",
                bracket = False )

        def product_on_basis( self, bt1, bt2 ):
            r'''
            The product of the F basis : concatenation of tree

            EXAMPLES:: FIXME:: tester !!!!
            '''
            def blr(btL, btR):
                if btR.is_empty(): return btL
                return self.indices()([btR[0], blr(btL, btR[1])], label = btR.label())
            def bll(btL, btR):
                if btR.is_empty(): return btL
                return self.indices()([bll(btL, btR[0]), btR[1]], label = btR.label())
            
            print bll( bt1, bt2 ).__pretty_repr__()
            print blr( bt1, bt2 ).__pretty_repr__()
            set = list( Set( bll( bt1, bt2 ).quasi_quot_greater() 
                 ).intersection(Set( blr( bt1, bt2 ).quasi_quot_smaller() )))
            monoms = []
            for tree in set:
                if (max(bt1.node_number(), bt2.node_number()) <= tree.node_number() 
                    and tree.node_number() <= bt1.node_number() + bt2.node_number()):
                    monoms.append(tree)
            return self.sum_of_monomials( monoms )
#            from all import WQSym
#            M = WQSym( self.base().base_ring() ).M()
#            return self( M( canonical_packed_word( bt1 ) ) * M( canonical_packed_word( bt2 ) ) )

        def coproduct_on_basis( self, p ):
            r"""
            The coproduct of the Q basis : de-shuffle of tree

            EXAMPLES::

                sage: from sage.combinat.pbt.classical import FundamentalDual
                sage: Q = FundamentalDual(QQ)
                sage: Q[6,4,5,2,1,3].coproduct()
                Q[] # Q[2, 1, 4, 6, 5, 3] + Q[1] # Q[1, 3, 5, 4, 2] + Q[2, 1] # Q[2, 4, 3, 1] + Q[2, 1, 3] # Q[1, 3, 2] + Q[2, 1, 4, 3] # Q[2, 1] + Q[2, 1, 4, 5, 3] # Q[1] + Q[2, 1, 4, 6, 5, 3] # Q[]
            """
            def restrict( bt ):
                if bt.is_empty():
                    yield ( bt, bt )
                    return
                for bm, bM in restrict( bt[1] ):
                    yield ( self.indices()( [bt[0], bm], label = bt.label() ), bM )
                for bm, bM in restrict( bt[0] ):
                    yield ( bm, self.indices()( [bM, bt[1]], label = bt.label() ) )
            return self.tensor_square().sum_of_monomials( restrict( p ) )
            
#            def restriction( w, i ):
#                left = []
#                right = []
#                for l in w :
#                    if l >= i + 1: right.append( l )
#                    else: left.append( l )
#                return ( self.phi( left ), self.phi( right ) )
#            w = canonical_packed_word( p )
#            return self.tensor_square().sum_of_monomials( 
#                     [restriction( w, i ) for i in range( max( w ) + 1 )] )

        _latex_term = latex_term
        _repr_term = repr_term
        __getitem__ = getitem_tree

    Qm = FundamentalDual

    class Complete( BindableClass, CombinatorialFreeModule ):
        r'''
        TESTS::
        '''
        def __init__( self, PBT ):
            CombinatorialFreeModule.__init__( 
                self, PBT.base_ring(), PBT.indices(),
                category = PBT.Bases(),
                prefix = "H", latex_prefix = "\\mathcal{H}^m",
                bracket = False )

        _latex_term = latex_term
        _repr_term = repr_term
        __getitem__ = getitem_tree

        def product_on_basis( self, t1, t2 ):
            Qm = self.realization_of().Qm()
            return self( Qm( self( t1 ) ) * Qm( self( t2 ) ) )

    H = Complete

    class Elem( BindableClass, CombinatorialFreeModule ):
        r'''
        TESTS::
        '''
        def __init__( self, PBT ):
            CombinatorialFreeModule.__init__( 
                self, PBT.base_ring(), PBT.indices(),
                category = PBT.Bases(),
                prefix = "E", latex_prefix = "\\mathcal{E}^m",
                bracket = False )

        _latex_term = latex_term
        _repr_term = repr_term
        __getitem__ = getitem_tree

        def product_on_basis( self, t1, t2 ):
            Qm = self.realization_of().Qm()
            return self( Qm( self( t1 ) ) * Qm( self( t2 ) ) )

    E = Elem

    class F( BindableClass, CombinatorialFreeModule ):
        r'''
        TESTS::
        '''
        def __init__( self, PBT ):
            CombinatorialFreeModule.__init__( 
                self, PBT.base_ring(), PBT.indices(),
                category = PBT.Bases(),
                prefix = "A", latex_prefix = "\\mathcal{F}^m",
                bracket = False )

        _latex_term = latex_term
        _repr_term = repr_term
        __getitem__ = getitem_tree

        def product_on_basis( self, t1, t2 ):
            Qm = self.realization_of().Qm()
            return self( Qm( self( t1 ) ) * Qm( self( t2 ) ) )

    A = F

    class G( BindableClass, CombinatorialFreeModule ):
        r'''
        TESTS::
        '''
        def __init__( self, PBT ):
            CombinatorialFreeModule.__init__( 
                self, PBT.base_ring(), PBT.indices(),
                category = PBT.Bases(),
                prefix = "B", latex_prefix = "\\mathcal{G}^m",
                bracket = False )

        _latex_term = latex_term
        _repr_term = repr_term
        __getitem__ = getitem_tree

        def product_on_basis( self, t1, t2 ):
            Qm = self.realization_of().Qm()
            return self( Qm( self( t1 ) ) * Qm( self( t2 ) ) )

    B = G

    class Gb( BindableClass, CombinatorialFreeModule ):
        r'''
        TESTS::
        '''
        def __init__( self, PBT ):
            CombinatorialFreeModule.__init__( 
                self, PBT.base_ring(), PBT.indices(),
                category = PBT.Bases(),
                prefix = "C", latex_prefix = "\\mathcal{G}^m",
                bracket = False )

        _latex_term = latex_term
        _repr_term = repr_term
        __getitem__ = getitem_tree

        def product_on_basis( self, t1, t2 ):
            Qm = self.realization_of().Qm()
            return self( Qm( self( t1 ) ) * Qm( self( t2 ) ) )

    C = Gb

    class D( BindableClass, CombinatorialFreeModule ):
        r'''
        TESTS::
        '''
        def __init__( self, PBT ):
            CombinatorialFreeModule.__init__( 
                self, PBT.base_ring(), PBT.indices(),
                category = PBT.Bases(),
                prefix = "D", latex_prefix = "\\mathcal{G}^m",
                bracket = False )

        _latex_term = latex_term
        _repr_term = repr_term
        __getitem__ = getitem_tree

        def product_on_basis( self, t1, t2 ):
            Qm = self.realization_of().Qm()
            return self( Qm( self( t1 ) ) * Qm( self( t2 ) ) )

    class F( BindableClass, CombinatorialFreeModule ):
        r'''
        TESTS::
        '''
        def __init__( self, PBT ):
            CombinatorialFreeModule.__init__( 
                self, PBT.base_ring(), PBT.indices(),
                category = PBT.Bases(),
                prefix = "F", latex_prefix = "\\mathcal{G}^m",
                bracket = False )

        _latex_term = latex_term
        _repr_term = repr_term
        __getitem__ = getitem_tree

        def product_on_basis( self, t1, t2 ):
            Qm = self.realization_of().Qm()
            return self( Qm( self( t1 ) ) * Qm( self( t2 ) ) )

    class A2( BindableClass, CombinatorialFreeModule ):
        r'''
        TESTS::
        '''
        def __init__( self, PBT ):
            CombinatorialFreeModule.__init__( 
                self, PBT.base_ring(), PBT.indices(),
                category = PBT.Bases(),
                prefix = "A2", latex_prefix = "\\mathcal{G}^m",
                bracket = False )

        _latex_term = latex_term
        _repr_term = repr_term
        __getitem__ = getitem_tree

        def product_on_basis( self, t1, t2 ):
            Qm = self.realization_of().Qm()
            return self( Qm( self( t1 ) ) * Qm( self( t2 ) ) )

    class B2( BindableClass, CombinatorialFreeModule ):
        r'''
        TESTS::
        '''
        def __init__( self, PBT ):
            CombinatorialFreeModule.__init__( 
                self, PBT.base_ring(), PBT.indices(),
                category = PBT.Bases(),
                prefix = "B2", latex_prefix = "\\mathcal{G}^m",
                bracket = False )

        _latex_term = latex_term
        _repr_term = repr_term
        __getitem__ = getitem_tree

        def product_on_basis( self, t1, t2 ):
            Qm = self.realization_of().Qm()
            return self( Qm( self( t1 ) ) * Qm( self( t2 ) ) )
PBTm = PlanarBinaryUnaryTreeFunctions
